#include "PrecompiledHeaders.h"
#include "Player.h"
#include "Clone.h"
#include "Constants.h"
#include "ReplayController.h"
#include "GameSession.h"
#include "Ball.h"
#include "MovingObject.h"
#include "InputDevice.h"
#include "CameraControl.h"
#include "ReplayRecorder.h"
#include "PodServerInterface.h"
#include "TeleportControl.h"
#include "PickupControl.h"
#include "TeleportController.h"
#include "ScoreObjectControl.h"
#include "ScoreToppleControl.h"

// incremented every time a player is created to ensure that no two players have
// the same ID
int Player::nextPlayerID = 1;

/*
 * Constructor. Takes the ball, goal and all moving/active objects in the level.
 * Can also take an input device to control the player with
 */
Player::Player(Ball *b, std::vector<MovingObject*> mos, std::vector<OgreNewt::Body*> aos, 
               TeleportControl *tc, PickupControl* p, ScoreObjectControl *soc, ScoreToppleControl *stc, 
               InputDevice *i_d)
{
    ID = Player::nextPlayerID;
    main = false;
    Player::nextPlayerID++;
    ball = b;
    teleport_control = tc;
    moving_objects = mos;
    active_objects = aos;
    pickup_control = p;
    score_object_control = soc;
    score_topple_control = stc;
    
    input_device = i_d;
    if(input_device) input_device->registerBall(ball);
    recorder = new ReplayRecorder();
    use_pod = false;
    recording = false;
    if (ball) ball->setPlayer(this);
    if (pickup_control) pickup_control->setPlayer(this);
    if (score_object_control) score_object_control->setPlayer(this);
    if (score_topple_control) score_topple_control->setPlayer(this);
    
    invert = speed = sensitive = false;
    teleporting = false;
    player_finished = false;
    
    visible = false;
}

Player::~Player()
{
    delete ball;
    UserControlledInputDevice *uid = dynamic_cast<UserControlledInputDevice*>(input_device);
    if (uid == NULL) // if not a UserControlledInputDevice then
        delete input_device;
    delete recorder;
    if(main) delete teleport_control;
    delete pickup_control;
    std::vector<MovingObject*>::iterator it;
    for (it = moving_objects.begin(); it != moving_objects.end(); it++)
        delete (*it);
}

/*
 * Make a copy of all the player's objects to create another player
 */
Player *Player::clone(Ogre::SceneManager *scene_mgr)
{
    std::stringstream ss;
    ss << "_clone_" << Player::nextPlayerID;
    Ogre::String suffix = ss.str();
    int new_player_id = Player::nextPlayerID;
    
    Ball *new_ball = NULL;
    if (ball)
        new_ball = ball->clone(scene_mgr, suffix, new_player_id);
    
    
    PickupControl* new_pickup = NULL;
    if (pickup_control)
        new_pickup = pickup_control->clone();
        
    TeleportControl* new_tc = NULL;
    if (teleport_control)
        new_tc = teleport_control->clone();
            
    ScoreObjectControl* new_soc = NULL;
    if (score_object_control)
        new_soc = score_object_control->clone();
            
    ScoreToppleControl* new_stc = NULL;
    if (score_topple_control)
        new_stc = score_topple_control->clone();
    
    std::vector<MovingObject*> new_moving_objects(0);
    std::vector<MovingObject*>::iterator mo_it;
    for(mo_it = moving_objects.begin(); mo_it != moving_objects.end(); mo_it++)
        new_moving_objects.push_back((*mo_it)->clone(scene_mgr, suffix, new_player_id));
    
    std::vector<OgreNewt::Body*> new_active_objects(0);
    std::vector<OgreNewt::Body*>::iterator ao_it;
    for(ao_it = active_objects.begin(); ao_it != active_objects.end(); ao_it++) {
        OgreNewt::Body *body = *(ao_it);
                
        OgreNewt::Body *new_body = OgreNewt::cloneBody(body, suffix, new_player_id);
        
        new_body->setStandardForceCallback();
	    new_body->getOgreNode()->setInitialState();
	    
        new_active_objects.push_back(new_body);
    }
    
    return new Player(new_ball, new_moving_objects, new_active_objects, 
               new_tc, new_pickup, new_soc, new_stc, NULL);
}

void Player::teleport(Ogre::Vector3 from_pos, Ogre::Vector3 to_pos, Ogre::Vector3 to_dir)
{
    temp = input_device;
    input_device = new TeleportController(from_pos , to_pos);
    input_device->registerBall(ball);
    teleport_time_left = Constants::TELEPORT_DURATION;
    if(temp->getCamera())
    {
        printf("Using camera direction for rotation\n");
        teleport_rotation = temp->getCamera()->getDirection().getRotationTo(to_dir, Ogre::Vector3::UNIT_Y).getYaw(false);
    }
    teleporting = true;
}

/*
 * Update the players position etc
 */
void Player::update(Ogre::Real time)
{
    if(teleporting)
    {
        
        teleport_time_left -= time;
        if(teleport_time_left < 0)
        {
            input_device->unregisterBall();
            delete input_device;
            input_device = temp;
            teleporting = false;
            ball->finishedTeleporting();
        }
        else
        {
            temp->updateBall(time); // has no effect but ensures input device is up to date
            if(temp->getCamera())
                temp->getCamera()->rotate(teleport_rotation * (time  / Constants::TELEPORT_DURATION));
        }
    }
    if(input_device) input_device->updateBall(time);
    if(use_pod && ball) ball->predictCollisions(time);
    if(recording && ball) recorder->addNewInputState(time, ball->getGravity());

    std::vector<MovingObject*>::iterator it;
    for(it = moving_objects.begin(); it != moving_objects.end(); it++)
        (*it)->frameStarted(time);
    
    if (ball)
        ball->frameStarted(time);
    
    if (pickup_control)
        pickup_control->frameStarted(time);
}

/*
 * Control this player with a different input device
 */
void Player::changeInputDevice(InputDevice *i_d)
{
    CameraControl *c = NULL;
    if(input_device)
    {
        input_device->unregisterBall();
        c = input_device->getCamera();
        input_device->useCamera(NULL);
        input_device->setInvert(false);
        input_device->setSensitive(false);
        input_device->setSpeed(false);
    }
    input_device = i_d;
    if(input_device)
    {
        input_device->registerBall(ball);
        input_device->useCamera(c);
        input_device->setInvert(invert);
        input_device->setSensitive(sensitive);
        input_device->setSpeed(speed);
    }
}

/*
 * Start recording this players movements to create a replay file
 */
void Player::startRecording()
{
    recorder->begin();
    recording = true;
}

/*
 * Stop the recording and save it
 */
void Player::stopRecording()
{
    recorder->end();
    recording = false;
}

std::string Player::saveReplay()
{
    replay_file = recorder->save();
	return replay_file;
}

void Player::setPlayState(PlayState *play_state)
{
    if (teleport_control)
        teleport_control->setPlayState(play_state);
   
    if (ball)
        ball->setPlayState(play_state);
    
    if (pickup_control)
        pickup_control->setPlayState(play_state);
        
    if (score_object_control)
        score_object_control->setPlayState(play_state);
        
    if (score_topple_control)
        score_topple_control->setPlayState(play_state);
}

/*
 * Reset all the positions etc of all the special objects as well as the camera
 * and the input device
 */
void Player::reset()
{
    if(teleporting)
    {
        ball->restartAfterTeleport();
        teleport_control->finishedTeleporting();
        teleporting = false;
        delete input_device;
        input_device = temp;
    }
    if(input_device) input_device->reset();
    std::vector<MovingObject*>::iterator m_it;
    std::vector<OgreNewt::Body*>::iterator a_it;
    ball->reset();
    if (teleport_control) teleport_control->reset();
    if (pickup_control) pickup_control->reset();
    if (score_object_control) score_object_control->reset();
    if (score_topple_control) score_topple_control->reset();
    for(m_it = moving_objects.begin(); m_it != moving_objects.end(); m_it++)
    {
        (*m_it)->reset();
    }
    for(a_it = active_objects.begin(); a_it != active_objects.end(); a_it++)
    {
        
        OgreNewt::Body *bod = (*a_it);
        Ogre::Vector3 init_pos = bod->getOgreNode()->getInitialPosition();
        Ogre::Quaternion init_orient = bod->getOgreNode()->getInitialOrientation();
        
        bod->setVelocity(Ogre::Vector3::ZERO);
        bod->setOmega(Ogre::Vector3::ZERO);
        bod->setPositionOrientation(init_pos, init_orient);
        bod->unFreeze();
        
        Ogre::SceneNode *scene_node = dynamic_cast<Ogre::SceneNode*>(bod->getOgreNode());
        if (scene_node)
        {
            Ogre::SceneNode::ObjectIterator obj = scene_node->getAttachedObjectIterator();
            while (obj.hasMoreElements())
            {
                Ogre::MovableObject *m_obj = obj.getNext();
                m_obj->setVisible(true);
            }
        }
    }
    if(recording) recorder->restart();
    player_finished = false;
    show();
}

void Player::setInvert(bool i)
{
    invert = i;
    if(input_device) input_device->setInvert(i);
}

void Player::setSensitive(bool s)
{
    sensitive = s;
    if(input_device) input_device->setSensitive(s);
}

void Player::setSpeed(bool s)
{
    speed = s;
    if(input_device) input_device->setSpeed(s);
}

void Player::show()
{
    visible = true;
    ball->setVisibility(true);
}

void Player::hide()
{
    visible = false;
    ball->setVisibility(false);
}
